package com.weilx959.spt.plugin.e2sp.utils.sql.deparser;

import com.weilx959.spt.plugin.e2sp.constant.SQLType;
import com.weilx959.spt.plugin.e2sp.exception.ExcelException;
import com.weilx959.spt.plugin.e2sp.model.statement.ErrorStatement;
import com.weilx959.spt.plugin.e2sp.model.statement.Statement;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.parser.CCJSqlParser;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.parser.ParseException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.*;

/**
 * sql 反解析器工厂
 *
 * @author weilx
 * @since 2024-07-01
 */
@Slf4j
public class SqlDeParserFactory {
    private SqlDeParserFactory() {}

    public static final Character SQL_SPLIT_CHAR = ';';

    /**
     * 解析
     *
     * @param sqlPath sql路径
     * @return {@link List }<{@link Statement }>
     */
    public static List<Statement> deParse(Path sqlPath) {
        List<String> sqls = new ArrayList<>();

        try (InputStream is = Files.newInputStream(sqlPath);
             BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
            StringBuilder sqlBuilder = new StringBuilder();
            int c = br.read();

            while (c != -1) {
                char ch = (char) c;

                if (ch == SQL_SPLIT_CHAR) {
                    String sql = sqlBuilder.toString();

                    if (StringUtils.isNotBlank(sql)) {
                        sqls.add(sql);
                        sqlBuilder = new StringBuilder();
                    }
                } else {
                    sqlBuilder.append(ch);
                }
                c = br.read();
            }
        } catch (IOException e) {
            log.error("Read sql file error", e);
            throw new ExcelException(sqlPath, "Read sql file error", e);
        }
        return deParse(sqls);
    }

    /**
     * 解解析
     *
     * @param sqls sqls
     * @return {@link List }<{@link Statement }>
     */
    public static List<Statement> deParse(List<String> sqls) {
        sqls = Optional.ofNullable(sqls)
            .map(s -> s.stream().filter(StringUtils::isNotBlank).toList())
            .orElse(null);

        if (CollectionUtils.isEmpty(sqls)) {
            return new ArrayList<>();
        }
        var countDownLatch = new CountDownLatch(sqls.size());
        var cachedThreadPool = Executors.newCachedThreadPool();
        List<Statement> statementList = new CopyOnWriteArrayList<>();

        for (String sql : sqls) {
            cachedThreadPool.execute(() -> {
                try {
                    Statement statement = SqlDeParserFactory.deParse(StringUtils.trim(sql));

                    if (Objects.nonNull(statement)) {
                        statement.setSql(sql);
                        statementList.add(statement);
                    }
                } catch (Exception e) {
                    log.error("Parse sql[" + sql + "] error", e);
                    statementList.add(ErrorStatement.builder().sql(sql).cause(e).build());
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        try {
            if (!countDownLatch.await(30, TimeUnit.SECONDS)) {
                throw new TimeoutException("SQL generation timeout");
            }
        } catch (InterruptedException | TimeoutException e) {
            log.error("SQL generation failed", e);
            Thread.currentThread().interrupt();
        } finally {
            cachedThreadPool.shutdown();
        }
        return statementList;
    }

    /**
     * 解析SQL
     *
     * @param sql sql
     * @return {@link Statement }
     * @throws ParseException 异常
     */
    public static Statement deParse(String sql) throws ParseException {
        if (StringUtils.isBlank(sql)) {
            return null;
        }
        CCJSqlParser ccjSqlParser = CCJSqlParserUtil.newParser(sql);
        var ccjStatement = ccjSqlParser.Statement();
        SQLDeParser deParser = Optional.ofNullable(SQLType.getSQLType(ccjStatement.getClass()))
            .map(SQLType::getDeParser)
            .orElse(null);

        if (Objects.isNull(deParser)) {
            throw new IllegalArgumentException("Unsupported sql type: " + ccjStatement.getClass());
        }
        Statement statement = deParser.deParse(ccjStatement);

        if (Objects.nonNull(statement)) {
            statement.setSql(sql);
        }
        return statement;
    }
}
